/*=====================================================================*
 *                   Copyright (C) 2011 Paul Mineiro                   *
 * All rights reserved.                                                *
 *                                                                     *
 * Redistribution and use in source and binary forms, with             *
 * or without modification, are permitted provided that the            *
 * following conditions are met:                                       *
 *                                                                     *
 *     * Redistributions of source code must retain the                *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer.                                       *
 *                                                                     *
 *     * Redistributions in binary form must reproduce the             *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer in the documentation and/or            *
 *     other materials provided with the distribution.                 *
 *                                                                     *
 *     * Neither the name of Paul Mineiro nor the names                *
 *     of other contributors may be used to endorse or promote         *
 *     products derived from this software without specific            *
 *     prior written permission.                                       *
 *                                                                     *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND              *
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,         *
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES               *
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE             *
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER               *
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,                 *
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES            *
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE           *
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR                *
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF          *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT           *
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY              *
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE             *
 * POSSIBILITY OF SUCH DAMAGE.                                         *
 *                                                                     *
 * Contact: Paul Mineiro <paul@mineiro.com>                            *
 *=====================================================================*/

#ifndef __HINGE_LOSS_HH__
#define __HINGE_LOSS_HH__

#include <algorithm>
#include <boost/optional.hpp>
#include <cmath>
#include <stdint.h>
#include "dyadicp.hh"
#include "loss.hh"
#include "frsqrt.hh"

namespace flassol
{
  class HingeLoss : public Loss
    {
      private:
        static LossFactory factory;

        uint64_t bad_labels;
        uint64_t skipped_update;

        uint64_t npos;
        uint64_t nneg;

      public:
        explicit
        HingeLoss () : bad_labels (0),
                       skipped_update (0),
                       npos (0),
                       nneg (0) 
          { 
          }

        virtual float 
        loss (float p,
              float z)
          {
            if (z != 1 && z != 0)
              {
                ++bad_labels;
              }

            float y = (z > 0) ? 1 : -1;

            if (y == 1) 
              { 
                ++npos; 
              } 
            else
              {
                ++nneg;
              }

            return std::max (0.0, 1.0 - y * p);
          }

        virtual float 
        dldp (float p,
              float z)
          {
            float y = (z > 0) ? 1 : -1;

            return (1.0 - y * p > 0.0) ? -y : 0;
          }

        virtual std::pair<float, float>
        invariant_update (float p,
                          float wdotx,
                          float adotb,
                          float z,
                          float eta,
                          float anormplusbnorm,
                          float xnorm,
                          bool  adaptive,
                          float adaptivexnorm,
                          float lambda)
          {
            float y = (z > 0) ? 1 : -1;

            if (1.0f - y * p <= 0.0f)
              {
                return std::make_pair (0.0f, 1.0f);
              }
            else
              {
                float actualeta = (adaptive) ? eta * rsqrtf (xnorm)
                                             : eta / xnorm;
                float xtx = (adaptive) ? adaptivexnorm : xnorm;
                float s = -y * actualeta;
                float kappa = lambda / y;

                std::pair<bool, float> ps = 
                  flassol::dyadic::p (s,
                                      adotb,
                                      anormplusbnorm,
                                      wdotx,
                                      xtx,
                                      kappa);

                if (1.0f - y * ps.second >= 0.0f)
                  {
                    // desired step is acceptable

                    if (ps.first)
                      {
                        ++skipped_update;

                        return std::make_pair (0.0f, 1.0f);
                      }

                    return std::make_pair (s, expf (kappa * s));
                  }

                // desired step is too big
                // instead take the maximum acceptable step
                
                std::pair<bool, float> pinv = 
                  flassol::dyadic::pinv (y,
                                         adotb,
                                         anormplusbnorm,
                                         wdotx,
                                         xtx,
                                         kappa);

                if (pinv.first)
                  {
                    ++skipped_update;

                    return std::make_pair (0.0f, 1.0f);
                  }
                else
                  {
                    return std::make_pair (pinv.second, 
                                           expf (kappa * pinv.second));
                  }
              }

            // in the case of no dyad, 
            // anormplusbnorm == 0 and p == wdotx
            // flassol::dyadic::pinv (y) = (wdotx - y) / xnorm
            //                           = -y (1 - y * wdotx) / xnorm
            //                           = -y (1 - y * p) / xnorm
            //
            // so this matches the original invariant_update
            //
            // (1.0 - y * p > 0.0) ? -y * std::min (eta, 1.0f - y * p) / norm 
          }

        virtual std::ostream& 
        report (std::ostream& o,
                LogLevel      level)
          {
            if (npos + nneg > 0 && level <= INFO)
              {
                double p =   static_cast<double> (npos) 
                           / static_cast<double> (npos + nneg);

                o << "unconditional probability of class 1 = " << p << std::endl;
                o << "best constant = " << (npos > nneg ? 1 : -1) << std::endl;
                o << "best constant loss = " << 2.0 * std::min (p, 1.0 - p) << std::endl;
              }

            if (bad_labels > 0 && level <= WARN)
              {
                o << "WARNING: encountered " << bad_labels 
                  << " labels that were not 1 or 0"
                  << std::endl;
                o << "WARNING: positives were mapped to 1"
                  << " non-positives were mapped to 0"
                  << std::endl;
              }

            if (skipped_update > 0 && level <= WARN)
              {
                o << "WARNING: skipped " << skipped_update
                  << " updates due to loss of precision "
                  << std::endl;
              }

            return o;
          }
    };
}

#endif // __HINGE_LOSS_HH__
